<?php

Raise::load('core.RaiseObject');
Raise::load('core.helper.convert.RaiseConvert');
Raise::load('core.io.file.RaiseFileSystem');
Raise::load('core.io.file.RaisePath');
Raise::load('core.datetime.RaiseDateTime');
Raise::load('core.io.file.RaiseFileStream');

/**
 * RaiseFile class
 * represents a file
 * 
 * @author Sam-Mauris Yong / hellclanner at live dot com
 * @license http://www.opensource.org/licenses/bsd-license New BSD License
 * @package phpraise.core.io.file
 * @since 1.2
 */
class RaiseFile extends RaiseObject {

    const TYPE_DIR = 'dir';
    const TYPE_FILE = 'file';
    const TYPE_SOCKET = 'socket';
    const TYPE_LINK = 'link';
    const TYPE_OTHERS = 'others';

    /**
     * Actual resolved pathname of the file
     * @var string
     */
    private $pathname;
    
    /**
     * Content of the file - for caching purposes
     * @var string|boolean String if the actual content has been loaded, FALSE otherwise.
     */
    private $content = false;

    /**
     * File Permissions - for caching purposes
     * @var integer|boolean Integer if the actual content has been loaded. FALSE otherwise.
     */
    private $perms = false;
    
    /**
     * Create a new RaiseFile object
     * @param string $f Pathname of the file
     */
    public function __construct($f){
        $this->pathname = $f;
    }

    /**
     * Create the file if file does not physically exists.
     */
    public function create(){
        if(!$this->exists()){
            $handle = fopen($this->pathname, 'w');
            fclose($handle);
        }
    }
    
    /**
     * Get the file size
     * @return integer
     */
    public function size(){
        return filesize($this->pathname);
    }

    /**
     * Get the permission of the file
     * @return integer
     * @link http://php.net/chmod
     */
    public function permission($p = null){
        if(func_num_args() == 1){
            $ok = chmod($this->pathname, $p);
            if(!$ok){
                throw new RaiseFileOperationFailedException('RaiseFile::permission() failed to perform permission change for file \'' . $this->pathname . '\'.');
            }
            return $p;
        }else{
            return fileperms($this->pathname);
        }
    }

    /**
     * Get the entire file content
     * @return string The entire file content is returned if successful.
     */
    public function read(){
        if($this->exists()){
            return file_get_contents($this->pathname);
        }else{
            throw new RaiseFileNotFoundException('Failed to get content of file \'' . $this->pathname . '\': File is not found.');
            return false;
        }
    }
    
    /**
     * Set the file content
     * @param string $c The content of the file
     */
    public function write($c){
        $ok = file_put_contents($this->pathname, $c);
        if(!$ok){
            throw new RaiseFileOperationFailedException('Failed to set contents of file \'' . $this->pathname . '\': Unknown Error.');
        }
    }

    /**
     * Append the file content
     * @param string $ctn The additional file content to append
     * @return bool TRUE if successful, FALSE otherwise
     */
    public function append($ctn){
        $link = fopen($this->pathname, 'a');
        if($link){
            $ok = fwrite($link, $ctn);
            if(!$ok){
                throw new RaiseFileOperationFailedException('Failed to append content to file \'' . $this->pathname . '\': Failed to write file.');
            }
            @fclose($link);
        }else{
            throw new RaiseFileOperationFailedException('Failed to append content to file \'' . $this->pathname . '\': Failed to open file.');
        }
    }

    /**
     * Get or set the Last Modified attribute of the file
     * @param RaiseDateTime $dt (optional) The datetime to set to
     * @return RaiseDateTime The last modified timestamp of the file.
     */
    public function lastModified($dt = null){
        if(func_num_args() == 1){
            touch($this->pathname, $dt->toTimestamp());
            return $dt;
        }else{
            return RaiseDateTime::fromTimestamp(filemtime($this->pathname));
        }
    }

    /**
     * Delete the file
     * @return boolean TRUE if successful, FALSE otherwise
     */
    public function delete(){
        if($this->exists()){
            return RaiseConvert::to(RaiseConvert::TO_BOOLEAN, unlink($this->pathname));
        }else{
            throw new RaiseFileNotFoundException('Deleting file \'' . $this->pathname . '\', but file is not found');
            return false;
        }
    }

    /**
     * Copy the file to another destination
     * @param string $dest The directory path to copy this file to
     * @return RaiseFile The RaiseFile object of the copied file is returned.
     */
    public function copy($dest){
        if(RaiseFileSystem::pathExist($dest)){
            $dest = RaisePath::combine($dest, RaisePath::baseName($this->pathname));
        }
        $ok = (bool)copy($this->pathname, $dest);
        if($ok){
            return new RaiseFile($dest);
        }else{
            throw new RaiseFileOperationFailedException('Failed to copy \'' . $this->pathname . '\' to \'' . $dest . '\'');
        }
    }

    /**
     * Get the entire pathname to the file
     * @return string
     */
    public function pathname(){
        return $this->pathname;
    }

    /**
     * Query whether the file exist or not
     * @return boolean TRUE if the file exists, FALSE otherwise.
     */
    public function exists(){
        return RaiseFileSystem::fileExist($this->pathname);
    }

    /**
     * Rename the file
     * @param string $newname The new name to give to the file
     * @return boolean TRUE if the rename is successful, FALSE otherwise.
     */
    public function rename($newname){
        $nn = RaisePath::path($this->pathname) . RaisePath::directorySeparator() . RaisePath::baseName($newname);
        if(rename($this->pathname, $nn)){
            $this->pathname = $nn;
            return true;
        }
        throw new RaiseFileOperationFailedException('Failed to rename \'' . $this->pathname . '\' to \'' . $newname . '\'');
        return false;
    }

    /**
     * Move the file to another directory path
     * @param string $newdir The new directory path to move the file to
     * @return boolean TRUE if the move operation is successful, FALSE otherwise.
     */
    public function move($newdir){
        $nd = $newdir . RaisePath::directorySeparator() . RaisePath::baseName($this->pathname);
        if(rename($this->pathname, $nd)){
            $this->pathname = $nd;
            return true;
        }
        throw new RaiseFileOperationFailedException('Failed to move \'' . $this->pathname . '\' to \'' . $newdir . '\'');
        return false;
    }

    /**
     * Get the RaiseFileStream of this file for IO streaming operations
     * @return RaiseFileStream
     */
    public function stream(){
        return new RaiseFileStream($this->pathname);
    }

    public function dispose() {
        parent::dispose();
        $this->content = false;
        $this->perms = false;
        $this->size = false;
    }
    
    /**
     * toString()
     * @return string
     */
    public function __toString(){
        return $this->pathname;
    }
    
}
